#include <Python.h>
#include "resource.h"
#include "PluginSettings.h"
#include "helpers.h"
#include "PyInteraction.h"
#include ".\DockingFeature\Docking.h"
#include <string>

using namespace std;

// Plugin Variables
NppData nppData;
ExecData execData;
const int nbFunc = 2;
FuncItem funcItems[nbFunc];
tTbData dockingData;

SciFnDirect pSciMsg;
sptr_t pSciWndData;

PyObject* globals;

SCNotification *scNotification;
string command = "", indent = "";
BOOL multiline = FALSE;

// Main
BOOL APIENTRY
DllMain(HANDLE hModule, DWORD  reasonForCall, LPVOID lpReserved)
{
    switch (reasonForCall)
	{
        case DLL_PROCESS_ATTACH:
            funcItems[0]._pFunc = pynpp_OpenCloseConsole;
			
            lstrcpy(funcItems[0]._itemName, L"Open/Close Console");
            funcItems[0]._init2Check = false;
			funcItems[0]._pShKey = NULL;
			
			funcItems[1]._pFunc = pynpp_About;
            lstrcpy(funcItems[1]._itemName, L"About pynpp");
            funcItems[1]._init2Check = false;
			funcItems[1]._pShKey = NULL;

			execData.hNPP = (HINSTANCE) hModule;

			/* Add a built-in module, before Py_Initialize */
			if (PyImport_AppendInittab("pynpp", PyInit_pynpp) == -1)
			{
				ERR_WIN(L"Append init pynpp error.");
			}
			Py_Initialize();

			globals = PyDict_New();

			if (PyDict_GetItemString(globals, "__builtins__") == NULL) {
				PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
			}

			// Optionally import the module; alternatively,
			//import can be deferred until the embedded script
			//imports it.
			//if (PyImport_ImportModule("pynpp") == NULL)
			//{
			//	ERR_WIN(L"Import module pynpp error.");
			//}

        break;
        case DLL_PROCESS_DETACH:
			SendNpp(NPPM_DESTROYSCINTILLAHANDLE, 0,
				execData.hConsoleScintilla);
			SendNpp(NPPM_MODELESSDIALOG, execData.hConsole,
				MODELESSDIALOGREMOVE);

			Py_DECREF(globals);
			DeletePynpp();
			Py_Finalize();
    }
    return TRUE;
}

// Notepad++ - Handles
extern "C" __declspec(dllexport) void setInfo(NppData notepadPlusData)
{
	nppData = notepadPlusData;
}

// Notepad++ - Plugin Name
extern "C" __declspec(dllexport) const TCHAR * getName()
{
    return PYNPP;
}

// Notepad++ - Functions
extern "C" __declspec(dllexport) FuncItem * getFuncsArray(int *nbF)
{
	*nbF = nbFunc;
	return funcItems;
}


#ifdef UNICODE
extern "C" __declspec(dllexport) BOOL isUnicode()
{
	return TRUE;
}
#endif //UNICODE

// Notepad++ - Messages from Scintilla
extern "C" __declspec(dllexport) void beNotified(SCNotification *notifyCode)
{
	if ((notifyCode->nmhdr.code == NPPN_READY) &&
		(notifyCode->nmhdr.hwndFrom == nppData._nppHandle))
	{
		// Fetch the menu
		execData.hMenu = GetMenu(nppData._nppHandle);

		// Create the docking dialog
		execData.hConsole = CreateDialog( execData.hNPP,
			MAKEINTRESOURCE(IDD_CONSOLE), nppData._nppHandle,
			(DLGPROC) pynpp_ConsoleProc);

		dockingData.hClient = execData.hConsole;
		dockingData.pszName = L"pynpp Console";
		dockingData.dlgID = 0;
		dockingData.uMask = DWS_DF_CONT_BOTTOM;
		dockingData.pszModuleName = L"pynpp.dll";

		// Register the docking dialog
		SendNpp(NPPM_DMMREGASDCKDLG, 0, &dockingData);

		SendNpp(NPPM_MODELESSDIALOG, execData.hConsole, MODELESSDIALOGADD);

		if ( (execData.hConsoleScintilla = (HWND)
			SendNpp(NPPM_CREATESCINTILLAHANDLE, 0, execData.hConsole)) == NULL)
		{
			ERR_WIN(L"Could not create Scintilla console component");
			return;
		}
		
		pSciMsg = (SciFnDirect)SendMessage(execData.hConsoleScintilla,
			SCI_GETDIRECTFUNCTION, 0, 0);
		pSciWndData = (sptr_t)SendMessage(execData.hConsoleScintilla,
			SCI_GETDIRECTPOINTER, 0, 0);

		// Setup Scintilla
		SendSci(SCI_SETEOLMODE, SC_EOL_LF, 0);
		SendSci(SCI_SETMARGINWIDTHN, 1, 0); // symbols
		SendSci(SCI_SETMARGINWIDTHN, 2, 0); // numbers
		SendSci(SCI_SETWRAPMODE, 1, 0);
		SendSci(SCI_STYLESETFONT, 0, (LPARAM) "Courier New");
		SendSci(SCI_STYLESETSIZE, 0, 10);
		ShowWindow(execData.hConsoleScintilla, SW_SHOW);

		execData.ConsoleOpen = FALSE;

		// TODO: uncomment to hide the console
		SendNpp(NPPM_SETMENUITEMCHECK, funcItems[0]._cmdID, FALSE);
		SendNpp(NPPM_DMMHIDE, 0, execData.hConsole);
		SetupPynpp();
	}
}

// Notepad++ - Messages from Notepad++
extern "C" __declspec(dllexport) LRESULT messageProc(UINT Message,
	WPARAM wParam, LPARAM lParam)
{
	return TRUE;
}

// Function to call the Console Scintilla directly
sptr_t SendSci(unsigned int iMessage, uptr_t wParam, sptr_t lParam)
{
    return pSciMsg(pSciWndData, iMessage, wParam, lParam);
}

// pynpp - About Window
void pynpp_About()
{
	PyObject *sys_str, *sys_module, *version_str;
	wchar_t *version;
	wchar_t msg[512];
	
	sys_str = PyUnicode_FromString("sys");
	sys_module = PyImport_Import(sys_str);
	Py_DECREF(sys_str);

	version_str = PyObject_GetAttrString(sys_module, "version");
	Py_DECREF(sys_module);

	version = PyUnicode_AsUnicode(version_str);
	Py_DECREF(version_str);

	lstrcpy(msg,
		L"Author: Joao Moreno 2007\nVersion: 0.1b\n\nPython Version:\n");
	lstrcat(msg, version);

	INFO_WIN(msg);
}

// pynpp - Open/Close Console Window
void
pynpp_OpenCloseConsole()
{
	if ( execData.ConsoleOpen )
	{
		SendNpp(NPPM_SETMENUITEMCHECK, funcItems[0]._cmdID, FALSE);
		SendNpp(NPPM_DMMHIDE, 0, execData.hConsole);
	}
	else
	{
		SendNpp(NPPM_SETMENUITEMCHECK, funcItems[0]._cmdID, TRUE);
		SendNpp(NPPM_DMMSHOW, 0, execData.hConsole);
	}

	//update the size of the console
	/*GetClientRect(execData.hConsole, &execData.rcConsole);
	SetWindowPos(execData.hConsoleScintilla, NULL, 0, 0,
		execData.rcConsole.right, execData.rcConsole.bottom, SWP_NOZORDER);*/

	execData.ConsoleOpen = !execData.ConsoleOpen;
}

// pynpp - Console Window Callback
BOOL CALLBACK
pynpp_ConsoleProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
		case WM_NOTIFY:
		{
			NMHDR* pnmh = (NMHDR*) lParam;
			if (pnmh->hwndFrom == nppData._nppHandle)
			{
				switch( LOWORD(pnmh->code) )
				{
				case DMN_CLOSE: // closing dialog
					SendNpp(NPPM_SETMENUITEMCHECK, funcItems[0]._cmdID, FALSE);
					execData.ConsoleOpen = FALSE;
					break;
				case DMN_FLOAT: // floating dialog
					break;
				case DMN_DOCK:  // docking dialog
					break;
				}
			}
			else if (pnmh->hwndFrom == execData.hConsoleScintilla)
			{
				switch( pnmh->code )
				{
					case SCN_CHARADDED:
						scNotification = (SCNotification*) lParam;
						int cPos;
						cPos = (int) SendSci(SCI_GETCURRENTPOS, 0,0);

						// New input line in console
						if (scNotification->ch == 10)
						{
							if ( SendSci(SCI_GETCURRENTPOS, 0, 0) < sciIN )
								SendSci(SCI_GOTOPOS, sciINEND, 0);

							// (Sort of) Prevent erasing of precedent text
							if (cPos <= sciIN || !multiline && cPos == sciIN+1)
							{
								PromptPy();
								break;
							}

							TextRange tr;
							char *code;
							int size;
							tr.chrg.cpMin = sciIN;
							tr.chrg.cpMax = cPos;
							size = cPos - sciIN + 1;
							code = (char*) malloc(size);
							tr.lpstrText = code;
							SendSci(SCI_GETTEXTRANGE, 0, (LPARAM) &tr);
							sciIN = cPos + 1;

							if (!multiline)
							{
								if (code[size-3] == ':')
								{
									indent.append("\t");
									command.append(code);
									multiline = TRUE;
								}
								else
								{
									PyObject* pyRet;
									pyRet = PyRun_String(code,
										Py_single_input, globals, globals);
									if (PyErr_Occurred()) PyErr_Print();
									Py_XDECREF(pyRet);
								}
							}
							else
							{
								BOOL endmultiline = TRUE;
								for (int i = 0; i < size-2; i++)
									if (code[i] != ' ' && code[i] != '\t')
									{
										endmultiline = FALSE;
										break;
									}

								if (endmultiline)
								{
									PyObject* pyRet;
									pyRet = PyRun_String(command.c_str(),
										Py_single_input, globals, globals);
									if (PyErr_Occurred()) PyErr_Print();
									Py_XDECREF(pyRet);

									indent.clear();
									command.clear();
									multiline = FALSE;
								}
								else
								{
									command.append(code);
									if (code[size-3] == ':')
										indent.append("\t");
								}
							}

							free(code);
							PromptPy(indent.c_str(), indent.size());
						}
						else
							sciINEND++;
						break;
					case SCN_UPDATEUI:
						/*if ( SendSci(SCI_GETCURRENTPOS, 0, 0) < sciIN )
							SendSci(SCI_GOTOPOS, sciINEND, 0);*/
						//update the size of the console
						GetClientRect(execData.hConsole, &execData.rcConsole);
						SetWindowPos(execData.hConsoleScintilla, NULL, 0, 0,
							execData.rcConsole.right, execData.rcConsole.bottom, SWP_NOZORDER);
					break;
				}
			}
		}
		break;
        case WM_COMMAND:
            switch (LOWORD(wParam))
            {
				case IDCANCEL:
				break;
            }
		break;
		//case WM_MOVE:
		case WM_SIZE:
			GetClientRect(execData.hConsole, &execData.rcConsole);
			SetWindowPos(execData.hConsoleScintilla, NULL, 0, 0,
				execData.rcConsole.right, execData.rcConsole.bottom, SWP_NOZORDER);
		break;
    }
    return FALSE;
}
